#ifndef __UVLOG_H
#define __UVLOG_H

#include <cstdio>
#include <cstdarg>
#include <pthread.h>

#ifdef DEBUG
#define LOG(tag, fmt, args...) do { \
    printf("%s %s:%d:%s(): " fmt "\n", tag, __FILE__, __LINE__, __func__, \
            args); \
} while (0);
#else
#define LOG(tag, fmt, args...)
#endif

namespace uv {
namespace log {

/** The type of log message: debug, info, warning, or error. */
enum Type_t { LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR };

/**
 * Print a debug message to the log using the following format:<br/>
 * DEBUG *tag* *msg*
 *
 * @param tag The tag used for the message.
 * @param fmt The string format styled like printf's format.
 */
void d(const char *tag, const char *fmt, ...);

/**
 * Print an info message to the log using the following format:<br/>
 * INFO *tag* *msg*
 *
 * @param tag The tag used for the message.
 * @param fmt The string format styled like printf's format.
 */
void i(const char *tag, const char *fmt, ...);

/**
 * Print a warning message to the log using the following format:<br/>
 * WARNING *tag* *msg*
 *
 * @param tag The tag used for the message.
 * @param fmt The string format styled like printf's format.
 */
void w(const char *tag, const char *fmt, ...);

/**
 * Print an error message to the log using the following format:<br/>
 * ERROR *tag* *msg*
 *
 * @param tag The tag used for the message.
 * @param fmt The string format styled like printf's format.
 */
void e(const char *tag, const char *fmt, ...);

/**
 * Open and manage a new file for the logger.
 *
 * @param fname The file name to open.
 * @param mode The mode used for opening the file.
 * @return True if the file was opened successfully.
 */
bool openFile(const char *fname, const char *mode);

/**
 * Set the logger's output file to a given file pointer. This file must be
 * closed by the user as it is not managed by the logger.
 *
 * @param _fptr The new file pointer to used for logging.
 * @return True if the new file pointer is now used.
 */
bool setFile(FILE *_fptr);

/**
 * this class performs logging operation to a file pointer. Initially, the
 * Log object will print to stdout.
 */
class Log {
public:
    /**
     * Build a default log printing to stdout.
     */
    Log();

    /**
     * If a file pointer is managed by this object, close it.
     */
    ~Log();

    /**
     * Open and manage a file.
     *
     * @param fname The file name to open.
     * @param mode The mode used for opening the file.
     * @return True if the file was opened successfully.
     */
    bool openFile(const char *fname, const char *mode);

    /**
     * Set the object's output file to a new file pointer. This file is not
     * managed by the object and must be closed by the user.
     *
     * @param _fptr The new file pointer to use for logging.
     * @return True if the new file pointer is now used.
     */
    bool setFile(FILE *_fptr);

    /**
     * Log a message with printf style arguments.
     *
     * @param type The type of log message to use (see {@link Type_t}).
     * @param tag The tag used for the log message.
     * @param fmt The printf style format string.
     */
    void log(Type_t type, const char *tag, const char *fmt, ...);

    /**
     * Log a message with vprintf style arguments.
     *
     * @param type The type of log message to use (see {@link Type_t}).
     * @param tag The tag used for the log message.
     * @param fmt The printf style format string.
     * @param args The arguments used to fill the printf format string.
     */
    void log(Type_t type, const char *tag, const char *fmt, va_list args);
private:
    /**
     * Print the string value of the type.
     *
     * @param type The {@link Type_t} value to print.
     */
    void printType(Type_t type);

    /**
     * If fptr is owned by the object, close it and use stdout.
     */
    void closeOwnedFile();

    /** The current file pointer to use for logging. */
    FILE *fptr;
    /** Tells if the current file pointer used is managed by the object. */
    bool is_file_owned;
    
    /** Mutex used to protect the fptr. */
    pthread_mutex_t fptr_mutex;
};

} // namespace log
} // namespace uv

#endif // __UVDEBUG_H
